// $Id: colshapemanager.cpp 470 2008-05-27 16:27:22Z agjermun $
// Implementation of the ColShapeManager
//
// ml278
// Copyright (C) 2008 Bjørn Øivind Bjørnsen, Aleksander Gjermundsen, Knut Andre Tidemann, Thomas Hansen
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

#include "colshapemanager.h"
#include <btBulletCollisionCommon.h>
#include <BulletCollision/CollisionShapes/btTriangleShape.h>
#include <map>

#include "less.h"

/**
 * Singleton
*/
template<> ColShapeManager* Ogre::Singleton<ColShapeManager>::ms_Singleton = 0;

/**
 * constructor with shape type and a half-size vector (box, cylinder)
 * @param s the type of shape
 * @param half a half-size vector
*/
ColShapeManager::ColShapeKey::ColShapeKey(shapeType s, Vector3 half)
{
	mShapeType = s;
	mData.halfExtents = new btVector3(half.x, half.y, half.z);
}

/**
 * constructor with shape type, radius and height (cone, capsule)
 * @param s the type of shape
 * @param radius a position vector
 * @param height the height of the shape
*/
ColShapeManager::ColShapeKey::ColShapeKey(shapeType s, btScalar radius, btScalar height)
{
	mShapeType = s;
	this->mData.radius = new btScalar(radius);
	this->mData.height = new btScalar(height);
}

/**
 * constructor with shape type and an array of vectors (triangle)
 * @param s the type of shape
 * @param p array of three vectors for constructing a triangle
*/
ColShapeManager::ColShapeKey::ColShapeKey(shapeType s, Vector3 p[3])
{
	mShapeType = s;
	this->mData.points = new btVector3[3];
	this->mData.points[0] = btVector3(p[0].x, p[0].y, p[0].z);
	this->mData.points[1] = btVector3(p[1].x, p[1].y, p[1].z);
	this->mData.points[2] = btVector3(p[2].x, p[2].y, p[2].z);
}

/**
 * constructor with shape type and radius (sphere)
 * @param s the type of shape
 * @param radius the radius of the shape
*/
ColShapeManager::ColShapeKey::ColShapeKey(shapeType s, btScalar radius)
{
	mShapeType = s;
	this->mData.radius = new btScalar(radius);
}

/**
 * constructor with shape type and the path to a COLLADA-file
 * @param s the type of shape
 * @param colladaFile a position vector
*/
ColShapeManager::ColShapeKey::ColShapeKey(shapeType s, String colladaFile)
{
	mShapeType = s;
	this->mData.colladaFile = new String(colladaFile);
}

/**
 * copy constructor
 * @param key the ColshapeKey to copy
*/
ColShapeManager::ColShapeKey::ColShapeKey(const ColShapeKey &key)
{
	// make a copy of the memory pointed to by pointers
	// deep copy
	mShapeType = key.mShapeType;
	switch(mShapeType)
	{
		case box: // fall through
		case cylinder:
			this->mData.halfExtents = new btVector3(*key.mData.halfExtents);
			break;
		case cone: // fall through
		case capsule:
			this->mData.radius = new btScalar(*key.mData.radius);
			this->mData.height = new btScalar(*key.mData.height);
			break;
		case sphere:
			this->mData.radius = new btScalar(*key.mData.radius);
			break;
		case triangle:
			this->mData.points = new btVector3[3];
			this->mData.points[1] = key.mData.points[1];
			this->mData.points[2] = key.mData.points[2];
			this->mData.points[3] = key.mData.points[3];
			break;
		case collada:
			this->mData.colladaFile = new String(*key.mData.colladaFile);
			break;
		default:
			break;
	}
}
/**
 * destructor
*/
ColShapeManager::ColShapeKey::~ColShapeKey()
{
	switch(mShapeType)
	{
		case box: // fall through
		case cylinder:
			delete mData.halfExtents;
			break;
		case cone: // fall through
		case capsule:
			delete mData.radius;
			delete mData.height;
			break;
		case sphere:
			delete mData.radius;
			break;
		case triangle:
			delete[] mData.points;
		case collada:
			delete mData.colladaFile;
		default:
			break;
	}
}

/**
 * default constructor
*/
ColShapeManager::ColShapeManager()
{
}

/**
 * destructor
*/
ColShapeManager::~ColShapeManager()
{
	// delete collision shapes
	map<ColShapeKey, pair<unsigned int, btCollisionShape*>, Less<ColShapeKey, ColShapeKey> >::iterator itr = collisionShapes.begin();
	while(itr != collisionShapes.end()) {
		//btCollisionShape *shape = itr->second;
		pair<unsigned int, btCollisionShape*> p = itr->second;
		delete p.second;
		itr++;
	}
}

/**
 * checks if a ColShapeKey (type of key) exists.
 * @param key the ColShapeKey to check
 * @return true if the shape exists
*/
bool ColShapeManager::shapeExists(ColShapeKey &key)
{
	map<ColShapeKey, pair<unsigned int, btCollisionShape*>, Less<ColShapeKey, ColShapeKey> >::iterator itr = collisionShapes.find(key);
	if(itr == collisionShapes.end())
		return(false);
	else
		return(true);
}

/**
 * retrieves a shape of the given type.
 * @param key the type of shape to retrieve
 * @return the shape in form of a btCollisionShape
*/
btCollisionShape *ColShapeManager::getShape(ColShapeKey key)
{
        //LogManager::getSingletonPtr()->logMessage("btCollisionShape *ColShapeManager::getShape(ColShapeKey &key)...");
	if(shapeExists(key)) {
		// increment refcount
		collisionShapes[key].first++;
		// return shape
		return(collisionShapes[key].second);
	}
	else {
		// create shape and insert into map
		btCollisionShape *shape;
		switch(key.mShapeType)
		{
			case box:
				//LogManager::getSingletonPtr()->logMessage("box shape");
				shape = new btBoxShape(*key.mData.halfExtents);
				break; 
			case cylinder:
				//LogManager::getSingletonPtr()->logMessage("cylinder shape");
				shape = new btCylinderShape(*key.mData.halfExtents);
				break;
			case cone:
				//LogManager::getSingletonPtr()->logMessage("cone shape");
				shape = new btConeShape(*key.mData.radius, *key.mData.height);
				break;
			case capsule:
				//LogManager::getSingletonPtr()->logMessage("capsule shape");
				shape = new btCapsuleShape(*key.mData.radius, *key.mData.height);
				break;
			case sphere:
				//LogManager::getSingletonPtr()->logMessage("sphere shape");
				shape = new btSphereShape(*key.mData.radius);
				break;
			case triangle:
				//LogManager::getSingletonPtr()->logMessage("triangle shape");
				shape = new btTriangleShape(key.mData.points[0], key.mData.points[1], key.mData.points[2]);
				break;
			case collada:
				//LogManager::getSingletonPtr()->logMessage("collada");
				// XXX: collada something here
				shape = new btSphereShape(1);
				break;
		}
		//LogManager::getSingletonPtr()->logMessage("inserting into map");
		pair<unsigned int, btCollisionShape*> p;
		// refcount
		p.first = 1;
		// shape
		p.second = shape;
		collisionShapes[key] = p;
		//LogManager::getSingletonPtr()->logMessage("returning");
		return(shape);
	}
}

/**
 * remove a shape
 * @param key the ColShapeKey (shape type) to remove
*/
void ColShapeManager::removeShape(ColShapeKey key)
{
	if(shapeExists(key))
	{
		// decrement refcount and remove if no object is using it
		if(--collisionShapes[key].first <= 0)
		{
			collisionShapes.erase(key);
		}
	}
}

/**
 * retrieves the singleton object
 * @return the singletonobject for the ColShapeManager
*/
ColShapeManager* ColShapeManager::getSingletonPtr(void)
{
	return ms_Singleton;
}

/**
 * retrieves a reference to the singleton object
 * @return a reference to the singletonobject for the ColShapeManager
*/
ColShapeManager& ColShapeManager::getSingleton(void)
{  
	assert( ms_Singleton );  return ( *ms_Singleton );  
}
